package cn.cellcom.agent.online.client;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.cellcom.adapter.service.GzhMessageCallback;
import cn.cellcom.agent.biz.TChannelBiz;
import cn.cellcom.agent.biz.TGroupBiz;
import cn.cellcom.agent.biz.TQuestionBiz;
import cn.cellcom.agent.biz.TSessionBiz;
import cn.cellcom.agent.common.AgentConstant;
import cn.cellcom.agent.common.AgentConstant.MEMBER_STATUS;
import cn.cellcom.agent.common.AgentConstant.RESULT;
import cn.cellcom.agent.common.AgentConstant.SESSION_STEP;
import cn.cellcom.agent.common.AgentConstant.WECHAT_STEP;
import cn.cellcom.agent.entity.TSettingEntity;
import cn.cellcom.agent.online.handler.LogHandler;
import cn.cellcom.agent.online.handler.SelfChatHandler;
import cn.cellcom.agent.online.message.ArchiveMessage;
import cn.cellcom.agent.online.message.BizMessage;
import cn.cellcom.agent.online.message.MessageConstant;
import cn.cellcom.agent.online.message.MessageConstant.ARCHIVE_EVENT;
import cn.cellcom.agent.online.message.MessageConstant.ARCHIVE_SHOW_POLICY;
import cn.cellcom.agent.online.message.MessageConstant.MESSAGE_NOTIFY;
import cn.cellcom.agent.online.message.MessageConstant.MULIT_TYPE;
import cn.cellcom.agent.online.message.QuestionListMessage;
import cn.cellcom.agent.online.wrapper.SessionWrapper;
import cn.cellcom.agent.pojo.TChannel;
import cn.cellcom.agent.pojo.TCrm;
import cn.cellcom.agent.pojo.TQuestion;
import cn.cellcom.agent.pojo.TSession;
import cn.cellcom.agent.pojo.TTrace;
import cn.cellcom.agent.pojo.TUser;
import cn.cellcom.agent.util.Belong;
import cn.cellcom.agent.util.JuheUtil;
import cn.cellcom.agent.util.SerializableUtils;
import cn.cellcom.agent.util.ThirdCallbackUtils;
import cn.cellcom.agent.util.ThreadPool;
import cn.cellcom.jar.reflect.JavaBase;
import cn.cellcom.jar.util.AU;
import cn.cellcom.jar.util.CopyUtil;
import cn.cellcom.jar.util.DT;
import cn.cellcom.jar.util.LogUtil;
import cn.cellcom.jar.util.MyException;
import cn.zhouyafeng.itchat4j.tlUtil.TulingClient;

public class VisitorClient extends Client {

	private Logger log = LoggerFactory.getLogger(this.getClass());

	public VisitorClient(TChannelBiz chbiz, TSessionBiz ssbiz, TGroupBiz gbiz, TQuestionBiz qbiz, String channel, TCrm crm, String httpSessionId) {
		super();
		this.chbiz = chbiz;
		this.sbiz = ssbiz;
		this.qbiz = qbiz;
		this.gbiz = gbiz;
		this.channel = channel;
		this.crm = crm;
		this.httpSessionId = httpSessionId;
		selfChatHandler = new SelfChatHandler(this, qbiz);
	}

	private SelfChatHandler selfChatHandler = null;

	private String httpSessionId;

	private TQuestionBiz qbiz = null;

	private TSessionBiz sbiz = null;

	private TGroupBiz gbiz = null;

	private TChannelBiz chbiz = null;

	/**
	 * 访问的渠道
	 */
	private String channel;

	/**
	 * 渠道类型
	 */
	private TChannel channelObject;

	public TChannel getChannelObject() {
		if (channelObject == null) {
			try {
				channelObject = chbiz.getChannelByNo(crm.getOrg(), channel);
			} catch (MyException e) {
				log.error("", e.getException());
			}
		}
		return channelObject;
	}

	/**
	 * 访客信息
	 */
	private TCrm crm;

	/**
	 * 会话信息
	 */
	private Map<String, String> pid2sid = new ConcurrentHashMap<String, String>();

	/**
	 * 每次访问的序号会变
	 */
	private Long sequence = System.currentTimeMillis();

	/**
	 * 微信的步骤
	 */
	private WECHAT_STEP wechatStep = null;

	/**
	 * 微信当前可选择的技能组
	 */
	private Map<String, String> wechatGroupIndex = new HashMap<String, String>();

	public Map<String, String> getWechatGroupIndex() {
		return wechatGroupIndex;
	}

	public WECHAT_STEP getWechatStep() {
		return wechatStep;
	}

	public void setWechatStep(WECHAT_STEP wechatStep) {
		this.wechatStep = wechatStep;
	}

	public Long getSequence() {
		return sequence;
	}

	public void setSequence() {
		this.sequence = System.currentTimeMillis();
	}

	public SelfChatHandler getChatHandler() {
		return selfChatHandler;
	}

	public SessionWrapper initSession(String pid, TSettingEntity setting, String ip, TTrace trace) throws MyException {
		String sid = this.getIdMap().get(pid);
		log.info("[{}] now to init session[exist sid : {}] for pid[{}]", this.getId(), sid, pid);
		SessionWrapper session;
		// 刷新界面,session还在
		if (sid == null) {
			TSession tSession = sbiz.getTalkingSessionOfVisitor(crm.getId());
			if (tSession == null) {
				tSession = new TSession();
				tSession.setMessageCount(0);
				tSession.setMessageCountWhenAgent(0);
				tSession.setAgentMessageCount(0);
				tSession.setSystemMessageCount(0);
				tSession.setChannel(channel);
				tSession.setCrm(crm.getId());
				tSession.setId(IDManager.getSid());
				tSession.setOrg(crm.getOrg());
				tSession.setPid(pid);
				tSession.setRequestTime(System.currentTimeMillis());
				tSession.setStep(AgentConstant.SESSION_STEP.LOGIN.name());
				tSession.setType(AgentConstant.SESSION_TYPE.S.name());
				tSession.setIp(ip);
				if (trace != null) {
					tSession.setTrace(trace.getId());
				}
				Belong bl = JuheUtil.ipBelong(ip);
				tSession.setProvince(bl.getProvince());
				tSession.setCity(bl.getCity());
				if (setting.getSelfPolicy() == AgentConstant.SELF_POLICY.DIRECT.ordinal()) {// 直接进入自助
					tSession.setStep(AgentConstant.SESSION_STEP.SELF.name());
					tSession.setRobotTime(System.currentTimeMillis());
				}
				sbiz.getDao().mySave(tSession);
				/*
				 * long ss = System.currentTimeMillis(); String cr = tSession.getCrm(); for (int
				 * i = 0; i < 100000; i++) { tSession.setId(IDManager.getSid()); if (i % 2 == 0)
				 * { tSession.setCrm(cr + "-" + i); }
				 * tSession.setRequestTime(tSession.getRequestTime() + i);
				 * sbiz.getDao().mySave(tSession); }
				 * System.out.println("----------------------------" +
				 * (System.currentTimeMillis() - ss));
				 */
				log.info("[{}] no session in db, so create one[{}]", this.getId(), tSession.getId());
			} else {
				log.info("[{}] exist db session[{}] ", this.getId(), tSession.getId());
				if (StringUtils.isBlank(tSession.getPid())) {
					tSession.setPid("default");
				}
			}
			// 先查询内存,是否存在
			session = SessionManager.getInstance().getPool(tSession.getPid()).getSession(tSession.getId());
			// 如果是首次创建,或者是group内存也不存在该会话包装则创建
			if (session == null) {
				session = SessionManager.getInstance().getPool(tSession.getPid()).create(tSession, setting);
				archive(session, ARCHIVE_EVENT.INIT_SSN, null, ARCHIVE_SHOW_POLICY.MANAGER_AND_AGENT);
			}
		} else {
			session = SessionManager.getInstance().getPool(this.getOrg()).getSession(sid);
			log.info("[{}] exist memory session[{}] for pid[{}]", this.getId(), sid, pid);
		}
		if (session == null) {
			log.info("[{}] init session[null] for pid[{}] fail.", this.getId(), pid);
		} else {
			log.info("[{}] init session[{}] for pid[{}] finish.", this.getId(), session.getId(), pid);
		}
		return session;
	}

	/**
	 * 会话发送到客户端
	 * 
	 * @param session
	 * @return
	 */
	public RESULT sendSession(TSession session) {
		BizMessage m = new BizMessage();
		m.setPid(session.getPid());
		m.setSid(session.getId());
		m.setCid(session.getCrm());
		m.setReceiver(session.getCrm());
		m.setObject(session);
		m.setEvent(MessageConstant.MESSAGE_EVENT.SESSION);
		return this.sendBizMessage(m);
	}

	/**
	 * 访客打开页面，监测是否已经存在会话，如果是则可以继续聊天
	 * 
	 * @param otherwise
	 * 
	 * @throws MyException
	 */
	public void notifyStatus(String pid, MESSAGE_NOTIFY otherwise) {
		SessionWrapper session = getSessionByPid(pid);
		if (session == null) {
			log.error("[{}] notify status, but session is null for pid[{}]", this.getId(), pid);
			// return;
		}
		TSession s = session.getSession();
		log.info("[{}] 's status step is [{}] in session[{}]", this.getId(), s.getStep(), session.getId());
		if (otherwise == null) {
			// 处于登陆状态
			if (s.getStep().equals(AgentConstant.SESSION_STEP.LOGIN.name())) {
			} else if (s.getStep().equals(AgentConstant.SESSION_STEP.SELF.name())) {
				if (session.isFirstSelf()) {
					session.setFirstSelf(false);
					session.getSystem().sysSendNotify(this.getId(), MESSAGE_NOTIFY.ENTER_SELF, null);
					// super.sysSendText(session,
					// session.getSetting().getEnterFirstTip());
					session.getSystem().sysSendText(session.getSetting().getEnterFirstTip());
					// 配置了想要推送的问题
					if (StringUtils.isNotBlank(session.getSetting().getSelfSendQuestion())) {
						this.sendQuestionList(channel, session);
					}
				} else {// 刷新界面时候的提示
					session.getSystem().sysSendNotify(this.getId(), MESSAGE_NOTIFY.WE_ARE_TALKING, null);
				}
			} else if (s.getStep().equals(AgentConstant.SESSION_STEP.QUEUE.name())) {// 处于排队，则通知排队的位置
				int n = QueueManager.getInstance().getQueue(session.getPid()).getPosition(session.getId()) + 1;
				long x = (System.currentTimeMillis() - session.getSession().getQueueTime()) / 1000;
				if (x >= session.getSetting().getQueueLimit() * 60) {// 如果过了很久再来打开页面，从数据库加载出来的是QUEUE，这个时候要判断是否超过了时间
					this.endSession(session.getId(), AgentConstant.SESSION_END_TYPE.BY_QUEUE_OVER_TIME);
				} else {
					String text = "{\"n\":\"" + n + "\", \"x\":\"" + x + "\"}";
					session.getSystem().sysSendNotify(this.getId(), MESSAGE_NOTIFY.QUEUE_POSITION, text);
				}
			} else if (s.getStep().equals(AgentConstant.SESSION_STEP.AGENT.name())) {// 坐席服务状态不发送任何通知，因为在join的时候已经提示了XX坐席为您服务
			}
		} else {
			session.getSystem().sysSendNotify(this.getId(), otherwise, null);
		}
	}

	/**
	 * 推送默认的问题列表
	 * 
	 * @param channel
	 * @param session
	 * @return
	 * @throws MyException
	 */
	public RESULT sendQuestionList(String channel, SessionWrapper session) {
		// 配置了推送的问题列表
		Object[] number = session.getSetting().getSelfSendQuestion().split(",");
		List<TQuestion> list = null;
		try {
			list = qbiz.getQuestionList(session.getPid(), number);
		} catch (MyException e) {
			LogUtil.e(this.getClass(), "[" + session.getId() + "]get question list error", e);
		}
		if (AU.isBlank(list)) {
			log.info("[{}] now to send queustion list, but it is null", this.getId());
			return RESULT.ERROR;
		}
		log.info("[{}] now to send queustion list [{}]", this.getId(), list.size());
		QuestionListMessage message = new QuestionListMessage(session, list);
		return super.sendBizMessage(message);
	}

	/**
	 * 结束会话，这里不应该设置step,这样才能观察到会话结束的时候处在哪个阶段
	 * 
	 * @param sid
	 * @param session_end_type：
	 *            <br>
	 *            1、BY_OFFLINE done<br>
	 *            2、BY_VISITOR done<br>
	 *            3、BY_SILENT done<br>
	 *            4、BY_AGENT <br>
	 */
	public void endSession(String sid, AgentConstant.SESSION_END_TYPE type) {
		SessionWrapper session = SessionManager.getInstance().getPool(this.getPid4Sid(sid)).getSession(sid);
		log.info("[{}] end the session[{}] by type[{}]", this.getId(), session.getId(), type);

		// 如果处于排队,那么要通知其他人变更了位置
		QueueManager.getInstance().getQueue(session.getPid()).removeQueue(session);

		TSession s = session.getSession();
		if (type.equals(AgentConstant.SESSION_END_TYPE.BY_OFFLINE)) {// 离线所结束的会话不需要发送信息
			if (this.getChannelObject().getType().equals(AgentConstant.CHANNEL_TYPE.WECHAT.name())
					|| this.getChannelObject().getType().equals(AgentConstant.CHANNEL_TYPE.QQ.name())) {
				session.getSystem().sysSendText(session.getSetting().getOvertimeEndSessionTip());
			}
		} else {
			// 访客自己结束会话
			if (type.equals(AgentConstant.SESSION_END_TYPE.BY_VISITOR)) {
				session.getSystem().sysSendText(session.getSetting().getVisitorEndSessionTip());
			} else if (type.equals(AgentConstant.SESSION_END_TYPE.BY_AGENT)) {
				session.getSystem().sysSendText(session.getSetting().getAgentEndSessionTip());
			}
			this.notifyStatus(session.getPid(), MESSAGE_NOTIFY.END_SESSION);
		}
		session.left(this, MEMBER_STATUS.END);

		// 放在最后更新数据库，因为可能在left的时候系统还有发送消息需要计算
		s.setEndTime(System.currentTimeMillis());
		s.setEndType(type.name());
		this.updateSessionDb(s);

		if (StringUtils.isNotBlank(session.getSetting().getCallUrl())
				&& session.getSetting().getCallMethod().contains(AgentConstant.ThirdCallback.endSession.name())) {
			final SessionWrapper session2 = session;
			final TSession s2 = s;
			ThreadPool.Execute(new Runnable() {
				@Override
				public void run() {
					ThirdCallbackUtils.call(AgentConstant.ThirdCallback.endSession, crm.getTid(), session2.getSetting().getAccessKey(),
							session2.getSetting().getCallUrl(), null, s2);

				}
			});
		}
	}

	/**
	 * 结束排队，结束排队也被认为是结束了会话（设置了结束事件），但是结束原因不能以END结束，否则数据无法体现结束的原因。
	 * 
	 * 如果是访客主动结束排队，则有下发事件告知排队以及被取消达到交互的显示效果。
	 * 
	 * @param pid
	 * @param type
	 *            访客自己关闭、系统检测超时丢弃
	 */
	public void cancelQueue(String sid, AgentConstant.SESSION_END_TYPE type) {
		SessionWrapper session = SessionManager.getInstance().getPool(this.getPid4Sid(sid)).getSession(sid);
		TSession s = session.getSession();
		if (s.getStep().equals(AgentConstant.SESSION_STEP.QUEUE.name())) {
			// 如果处于排队,那么要通知其他人变更了位置
			QueueManager.getInstance().getQueue(session.getPid()).removeQueue(session);
			/**
			 * 1、修改session内存<br>
			 * 3、通知会话<br>
			 * 4、通知状态<br>
			 * 5、其他 2、存储<br>
			 */
			if (type.equals(AgentConstant.SESSION_END_TYPE.BY_VISITOR)) {
				session.getSystem().sysSendNotify(this.getId(), MESSAGE_NOTIFY.QUEUE_CANCEL_ED, null);
			} else if (type.equals(AgentConstant.SESSION_END_TYPE.BY_QUEUE_OVER_TIME)) {
				session.getSystem().sysSendNotify(this.getId(), MESSAGE_NOTIFY.QUEUE_OVER_TIME, null);
			}
			session.left(this, MEMBER_STATUS.OFFLINE);

			s.setEndTime(System.currentTimeMillis());
			s.setEndType(type.name());
			this.updateSessionDb(s);
			log.info("[{}] 's session[{}] in queue is canceled by type[{}]", this.getId(), s.getId(), type);
		} else {
			session.getSystem().sysSendNotify(this.getId(), MESSAGE_NOTIFY.ERROR, null);
			log.error("[{}] 's session[{}] in queue is canceled by type[{}], but it is not queue step[{}]", this.getId(), s.getId(), type,
					s.getStep());
		}
	}

	/**
	 * 会话被坐席接入
	 * 
	 * @param agent
	 * 
	 * @param pid
	 * @param type
	 */
	public boolean queueReceived(SessionWrapper session, TUser agent) {
		// 被接入后重新计算访客的最后消息时间
		session.setLastVisitorMessage();

		TSession s = session.getSession();
		// 移除队列元素并发出通知
		QueueManager.getInstance().getQueue(session.getPid()).removeQueue(session);
		/**
		 * 1、修改session内存<br>
		 * 2、存储<br>
		 * 3、通知会话<br>
		 * 4、通知状态<br>
		 * 5、其他
		 */
		s.setAgentTime(System.currentTimeMillis());
		s.setAgent(agent.getUsername());
		s.setAgentName(agent.getName());
		s.setStep(AgentConstant.SESSION_STEP.AGENT.name());
		this.updateSessionDb(s);
		this.sendSession(session.getSession());
		this.notifyStatus(session.getPid(), null);
		// 归档
		archive(session, ARCHIVE_EVENT.QUEUE_RECEIVE_ED, null);

		log.info("[{}] 's queue[{}] is received", this.getId(), s.getId());
		return true;

		/*
		 * if (s.getStep().equals(AgentConstant.SESSION_STEP.QUEUE.name())) { } else {
		 * log.error("[{}] 's queue[{}] is received, but it is not queue step[{}]",
		 * this.getId(), s.getId(), s.getStep()); return false; }
		 */
	}

	/**
	 * session失效了
	 */
	public void overtime() {
		log.info("[{}] is offline, now to left all session[{}]", this.getId(), this.getIdMap().size());
		for (String pid : this.getIdMap().keySet()) {
			SessionWrapper session = getSessionByPid(pid);
			archive(session, ARCHIVE_EVENT.OFFLINE, null);

			log.info("[{}] is offline, now to left the session[{}]", this.getId(), session.getId());
			// 访客不在线，这个调用到底要还是不要？调用之后在endSession中找不到this.getPid4Sid(sid)
			//session.left(this, MEMBER_STATUS.OFFLINE);
			// 如果处于自助、排队则需要结束会话，如果处于AGENT则不需要结束了【有可能过一段时间访客继续上线】
			if (SESSION_STEP.valueOf(session.getSession().getStep()).ordinal() < AgentConstant.SESSION_STEP.AGENT.ordinal()) {
				log.info("[{}] is offline, and the session[{}] 's step[{}] less than agent step, so end it", this.getId(), session.getId(),
						session.getSession().getStep());
				endSession(session.getId(), AgentConstant.SESSION_END_TYPE.BY_OFFLINE);
			}
		}
		// 访客端超时，移除对象[对于微信/QQ客户端超时结束会话时还需要发送消息,因此client要放到后面才进行移出]
		ClientManager.getInstance().remove(this.getId());
	}

	/**
	 * 调用数据库更新会话
	 */
	public void updateSessionDb(TSession session) {
		try {
			sbiz.getDao().myUpdate(session);
		} catch (MyException e) {
			LogUtil.e(this.getClass(), "[" + session.getId() + "]update session error", e);
		}
	}

	public String getChannel() {
		return channel;
	}

	public TCrm getCrm() {
		return crm;
	}

	public void setCrm(TCrm crm) {
		this.crm = crm;
	}

	public void archive(SessionWrapper session, ARCHIVE_EVENT event, String text) {
		this.archiveWithId(session, IDManager.getMid(), event.toString(), text, MULIT_TYPE.TEXT.name(), null);
	}

	public void archive(SessionWrapper session, ARCHIVE_EVENT event, String text, ARCHIVE_SHOW_POLICY showPolicy) {
		this.archiveWithId(session, IDManager.getMid(), event.toString(), text, MULIT_TYPE.TEXT.name(), null, showPolicy);
	}

	public void archiveWithId(SessionWrapper session, String mid, String event, String text, String mtype, Object obj) {
		this.archiveWithId(session, mid, event, text, mtype, obj, ARCHIVE_SHOW_POLICY.ALL);
	}

	public void archiveWithId(SessionWrapper session, String mid, String event, String text, String mtype, Object obj,
			ARCHIVE_SHOW_POLICY showPolicy) {
		this.archiveWithId(session, mid, event, text, mtype, obj, showPolicy, null);
	}

	public void archiveWithId(SessionWrapper session, String mid, String event, String text, String mtype, Object obj, ARCHIVE_SHOW_POLICY showPolicy,
			String pid) {
		String sid = null;
		if (session != null) {
			TSession tsession = session.getSession();
			sid = tsession.getId();
			pid = tsession.getPid();
		}
		ArchiveMessage am = new ArchiveMessage();
		try {
			// am.setCode();
			am.setEvent(event);
			am.setId(mid);
			if (obj != null) {
				if (JavaBase.isJavaBase(obj.getClass())) {
					am.setObject(String.valueOf(obj));
				} else {
					am.setObject(new String(SerializableUtils.serialize(obj)));
				}
			}
			am.setOrg(this.getOrg());
			am.setPid(pid);
			am.setSender(this.getId());
			am.setSenderNickname(this.getNickname());
			am.setSenderRole(AgentConstant.USER_TYPE_VISITOR);
			am.setShowPolicy(showPolicy.name());
			am.setText(text);
			am.setSid(sid);
			am.setCid(this.getCrm().getId());
			am.setTime(DT.getNow2());
			am.setMultiType(mtype);
			LogHandler.archive(am);
		} catch (Exception e) {
			log.error("Archive visitor message fail:" + am, e);
		}
	}

	@Override
	public String getId() {
		return crm.getId();
	}

	@Override
	public String getHttpSessionId() {
		return httpSessionId;
	}

	@Override
	public Map<String, String> getIdMap() {
		return pid2sid;
	}

	/**
	 * sid所对应的pid
	 * 
	 * @param sid
	 * @return
	 */
	public String getPid4Sid(String sid) {
		for (String pid : pid2sid.keySet()) {
			if (pid2sid.get(pid).equals(sid)) {
				return pid;
			}
		}
		return null;
	}

	@Override
	public String getOrg() {
		return crm.getOrg();
	}

	@Override
	public String getNickname() {
		return crm.getName();
	}

	@Override
	public String getSender(TChannel tchannel) {
		return crm.getId();
	}

	/**
	 * 访客访问pid的会话
	 * 
	 * @param pid
	 * @return
	 */
	public SessionWrapper getSessionByPid(String pid) {
		if (StringUtils.isBlank(pid)) {
			return null;
		}
		String sid = this.getIdMap().get(pid);
		if (StringUtils.isBlank(sid)) {
			return null;
		}
		return SessionManager.getInstance().getPool(pid).getSession(sid);
	}

	/**
	 * 访客端发送消息
	 */
	protected boolean receiveMessage(BizMessage m) {
		// 微信渠道，直接发送消息
		if (((VisitorClient) this).getChannelObject().getType().equals(AgentConstant.CHANNEL_TYPE.WECHAT.name())) {
			if (StringUtils.isNotEmpty(m.getText()) || m.getEvent().equals(MessageConstant.MESSAGE_EVENT.NOTIFY)) {
				if (m.getEvent().equals(MessageConstant.MESSAGE_EVENT.NOTIFY)) {
					// m.setText("this is a notify " + m.getNotify());
					m.setText(AgentWechatNotify.get(m.getNotify(), m.getObject()));
				}
				if (StringUtils.isNotBlank(m.getText())) {
					cn.cellcom.rpc.entity.ChatMessage dest = new cn.cellcom.rpc.entity.ChatMessage();
					try {
						CopyUtil.copyWithSuperClass(m, dest, null, true);
					} catch (MyException e) {
						e.printStackTrace();
					}
					GzhMessageCallback.getInstance().remoteChat(dest, "");
				}
			}
			return true;
		} else {
			return super.receiveMessage(m);
		}
	}
}
